﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Représente un couleur selon la norme CIE XYZ
    /// </summary>
    public struct CIEXYZ
    {
        private static readonly List<Tuple<double, CIEXYZ>> lambdaTable = CreateLambdaTable();

        /// <summary>
        /// Composante X de la couleur
        /// </summary>
        public double X;
        /// <summary>
        /// Composante Y de la couleur
        /// </summary>
        public double Y;
        /// <summary>
        /// Composante Z de la couleur
        /// </summary>
        public double Z;
        
        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="CIEXYZ"/>
        /// </summary>
        /// <param name="x">Composante X de la couleur</param>
        /// <param name="y">Composante Y de la couleur</param>
        /// <param name="z">Composante Z de la couleur</param>
        public CIEXYZ(double x, double y, double z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        /// <summary>
        /// Crée une couleur à partir d'une longueur d'onde
        /// </summary>
        /// <param name="lambda">Longueur d'onde de la couleur</param>
        /// <returns>Retourne la couleur correspondant à la longueur d'onde spécifée</returns>
        public static CIEXYZ FromLambda(double lambda)
        {
            for (int i = 0; i < lambdaTable.Count; i++)
            {
                double a = lambdaTable[i].Item1;
                double b = lambdaTable[i + 1].Item1;
                if (a <= lambda && b >= lambda)
                {
                    CIEXYZ min = lambdaTable[i].Item2;
                    CIEXYZ max = lambdaTable[i + 1].Item2;
                    double coeff = (b - lambda) / (b - a);
                    double comp = 1 - coeff;
                    double x = min.X * coeff + max.X * comp;
                    double y = min.Y * coeff + max.Y * comp;
                    double z = min.Z * coeff + max.Z * comp;
                    return new CIEXYZ(x, y, z);
                }
            }
            throw new ArgumentOutOfRangeException("lambda");
        }

        /// <summary>
        /// Converti une couleur CIEXYZ en couleur RGB
        /// </summary>
        /// <returns>Retourn un <see cref="System.Windows.Media.Color"/> équivalent</returns>
        public Color ToRGB()
        {
            double[] clinear = new double[3];
            clinear[0] = +3.2406 * X - 1.5372 * Y - 0.4986 * Z;
            clinear[1] = -0.9689 * X + 1.8758 * Y + 0.0415 * Z;
            clinear[2] = +0.0557 * X - 0.2040 * Y + 1.0570 * Z;
            
            clinear[0] = Math.Max(0.0, Math.Min(clinear[0], 1.0));
            clinear[1] = Math.Max(0.0, Math.Min(clinear[1], 1.0));
            clinear[2] = Math.Max(0.0, Math.Min(clinear[2], 1.0));

            for (int i = 0; i < 3; i++)
            {
                clinear[i] = ((clinear[i] <= 0.0031308) ?
                    12.92 * clinear[i] :
                    (1 + 0.055) * Math.Pow(clinear[i], (1.0 / 2.4)) - 0.055);
            }

            byte r = (byte)(clinear[0] * 255.0);
            byte g = (byte)(clinear[1] * 255.0);
            byte b = (byte)(clinear[2] * 255.0);

            return Color.FromArgb(255, r, g, b);
        }

        private static List<Tuple<double, CIEXYZ>> CreateLambdaTable()
        {
            List<Tuple<double, CIEXYZ>> returnValue = new List<Tuple<double,CIEXYZ>>();
            returnValue.Add(new Tuple<double, CIEXYZ>(380, new CIEXYZ(0.000767418377650623, 2.18781555031976E-05, 0.00361831033322114)));
            returnValue.Add(new Tuple<double, CIEXYZ>(385, new CIEXYZ(0.00125434758218333, 3.59026141590935E-05, 0.00591832155278806)));
            returnValue.Add(new Tuple<double, CIEXYZ>(390, new CIEXYZ(0.00238023112307865, 6.73174015483002E-05, 0.0112476158420285)));
            returnValue.Add(new Tuple<double, CIEXYZ>(395, new CIEXYZ(0.00429148434870414, 0.000121732301133176, 0.0203130259171996)));
            returnValue.Add(new Tuple<double, CIEXYZ>(400, new CIEXYZ(0.0080276001346348, 0.000222147425109391, 0.0380623807921014)));
            returnValue.Add(new Tuple<double, CIEXYZ>(405, new CIEXYZ(0.013009087849209, 0.000359026141590935, 0.0618198137551891)));
            returnValue.Add(new Tuple<double, CIEXYZ>(410, new CIEXYZ(0.0244081678447212, 0.000678783798945361, 0.116346909009312)));
            returnValue.Add(new Tuple<double, CIEXYZ>(415, new CIEXYZ(0.0435487490182879, 0.00122293279479412, 0.208291259957366)));
            returnValue.Add(new Tuple<double, CIEXYZ>(420, new CIEXYZ(0.0753842701671715, 0.00224391338494334, 0.362167620329855)));
            returnValue.Add(new Tuple<double, CIEXYZ>(425, new CIEXYZ(0.12048131942107, 0.0040951419275216, 0.582884550656345)));
            returnValue.Add(new Tuple<double, CIEXYZ>(430, new CIEXYZ(0.159261752496354, 0.00650734881633569, 0.777291596544373)));
            returnValue.Add(new Tuple<double, CIEXYZ>(435, new CIEXYZ(0.184281386738472, 0.00944687535061147, 0.910445416806911)));
            returnValue.Add(new Tuple<double, CIEXYZ>(440, new CIEXYZ(0.195377538427017, 0.0129025019634242, 0.980062829574778)));
            returnValue.Add(new Tuple<double, CIEXYZ>(445, new CIEXYZ(0.195254123190845, 0.0167171547178279, 1)));
            returnValue.Add(new Tuple<double, CIEXYZ>(450, new CIEXYZ(0.188600920004488, 0.0213171771569617, 0.994115337147986)));
            returnValue.Add(new Tuple<double, CIEXYZ>(455, new CIEXYZ(0.178783798945361, 0.0269269606193201, 0.97840233366992)));
            returnValue.Add(new Tuple<double, CIEXYZ>(460, new CIEXYZ(0.163132503085381, 0.0336587007741501, 0.936385055536856)));
            returnValue.Add(new Tuple<double, CIEXYZ>(465, new CIEXYZ(0.140861662739818, 0.0414562997868282, 0.85723101088298)));
            returnValue.Add(new Tuple<double, CIEXYZ>(470, new CIEXYZ(0.109592729720633, 0.0510378099405363, 0.722338157747111)));
            returnValue.Add(new Tuple<double, CIEXYZ>(475, new CIEXYZ(0.0797150230001122, 0.0631661617861551, 0.584483338943117)));
            returnValue.Add(new Tuple<double, CIEXYZ>(480, new CIEXYZ(0.0536519690339953, 0.0779872096937058, 0.456047346572422)));
            returnValue.Add(new Tuple<double, CIEXYZ>(485, new CIEXYZ(0.0325086951643667, 0.0949736340177269, 0.345674856950522)));
            returnValue.Add(new Tuple<double, CIEXYZ>(490, new CIEXYZ(0.0179569168630091, 0.116694715583978, 0.260955907101986)));
            returnValue.Add(new Tuple<double, CIEXYZ>(495, new CIEXYZ(0.00824638168966678, 0.145069000336587, 0.198193649725121)));
            returnValue.Add(new Tuple<double, CIEXYZ>(500, new CIEXYZ(0.00274879389655559, 0.181196005834175, 0.152586110176147)));
            returnValue.Add(new Tuple<double, CIEXYZ>(505, new CIEXYZ(0.001346348030966, 0.228486480421856, 0.119095702905868)));
            returnValue.Add(new Tuple<double, CIEXYZ>(510, new CIEXYZ(0.00521709861999327, 0.282172108156625, 0.0887467743745091)));
            returnValue.Add(new Tuple<double, CIEXYZ>(515, new CIEXYZ(0.0163244698754628, 0.341187030180635, 0.0626612812745428)));
            returnValue.Add(new Tuple<double, CIEXYZ>(520, new CIEXYZ(0.0354930999663413, 0.398294625827443, 0.0438965555929541)));
            returnValue.Add(new Tuple<double, CIEXYZ>(525, new CIEXYZ(0.0614832267474476, 0.444968024234265, 0.0321160103220016)));
            returnValue.Add(new Tuple<double, CIEXYZ>(530, new CIEXYZ(0.0928419163020307, 0.48356333445529, 0.0236508470773028)));
            returnValue.Add(new Tuple<double, CIEXYZ>(535, new CIEXYZ(0.12664086166274, 0.513211040053854, 0.0167395938516773)));
            returnValue.Add(new Tuple<double, CIEXYZ>(540, new CIEXYZ(0.162908111746887, 0.535173342308987, 0.0113878604285875)));
            returnValue.Add(new Tuple<double, CIEXYZ>(545, new CIEXYZ(0.20178391114103, 0.549927072814989, 0.00751710983956019)));
            returnValue.Add(new Tuple<double, CIEXYZ>(550, new CIEXYZ(0.243156064175923, 0.558145405587344, 0.00490856052956356)));
            returnValue.Add(new Tuple<double, CIEXYZ>(555, new CIEXYZ(0.287248962190059, 0.560978346235835, 0.00322562549085605)));
            returnValue.Add(new Tuple<double, CIEXYZ>(560, new CIEXYZ(0.333501626837204, 0.558173454504656, 0.00218781555031976)));
            returnValue.Add(new Tuple<double, CIEXYZ>(565, new CIEXYZ(0.380567710086391, 0.548973409626388, 0.00154269045214855)));
            returnValue.Add(new Tuple<double, CIEXYZ>(570, new CIEXYZ(0.42752159766633, 0.534051385616515, 0.00117805452709525)));
            returnValue.Add(new Tuple<double, CIEXYZ>(575, new CIEXYZ(0.472624256703691, 0.513519578144284, 0.0010097610232245)));
            returnValue.Add(new Tuple<double, CIEXYZ>(580, new CIEXYZ(0.514024458655896, 0.488051161225177, 0.000925614271289128)));
            returnValue.Add(new Tuple<double, CIEXYZ>(585, new CIEXYZ(0.548973409626388, 0.457926624032312, 0.000785369684730169)));
            returnValue.Add(new Tuple<double, CIEXYZ>(590, new CIEXYZ(0.575732076741838, 0.424660608100527, 0.000617076180859419)));
            returnValue.Add(new Tuple<double, CIEXYZ>(595, new CIEXYZ(0.592785818467407, 0.389823852799282, 0.000560978346235835)));
            returnValue.Add(new Tuple<double, CIEXYZ>(600, new CIEXYZ(0.595871199371704, 0.353977336474812, 0.000448782676988668)));
            returnValue.Add(new Tuple<double, CIEXYZ>(605, new CIEXYZ(0.586558958824189, 0.317962526646471, 0.000336587007741501)));
            returnValue.Add(new Tuple<double, CIEXYZ>(610, new CIEXYZ(0.562436889936048, 0.282172108156625, 0.000190732637720184)));
            returnValue.Add(new Tuple<double, CIEXYZ>(615, new CIEXYZ(0.526422080107708, 0.247503646359251, 0.0001346348030966)));
            returnValue.Add(new Tuple<double, CIEXYZ>(620, new CIEXYZ(0.47932794794121, 0.213732749915853, 0.000106585885784809)));
            returnValue.Add(new Tuple<double, CIEXYZ>(625, new CIEXYZ(0.421519129361607, 0.180074049141703, 5.60978346235835E-05)));
            returnValue.Add(new Tuple<double, CIEXYZ>(630, new CIEXYZ(0.360372489621901, 0.148659261752496, 2.80489173117918E-05)));
            returnValue.Add(new Tuple<double, CIEXYZ>(635, new CIEXYZ(0.303994165825199, 0.121732301133176, 1.68293503870751E-05)));
            returnValue.Add(new Tuple<double, CIEXYZ>(640, new CIEXYZ(0.251262201279031, 0.0981712105912712, 1.12195669247167E-05)));
            returnValue.Add(new Tuple<double, CIEXYZ>(645, new CIEXYZ(0.202400987321889, 0.0775272074497924, 5.60978346235835E-06)));
            returnValue.Add(new Tuple<double, CIEXYZ>(650, new CIEXYZ(0.159037361157859, 0.0600246830472344, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(655, new CIEXYZ(0.122685964321777, 0.0457758330528442, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(660, new CIEXYZ(0.0925053292942892, 0.034219679120386, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(665, new CIEXYZ(0.0679905755637832, 0.0250084146751935, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(670, new CIEXYZ(0.049029507461012, 0.0179513070795467, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(675, new CIEXYZ(0.0356782228205991, 0.0130146976326714, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(680, new CIEXYZ(0.02623695725345, 0.0095366318860092, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(685, new CIEXYZ(0.018456187591159, 0.00668686188713116, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(690, new CIEXYZ(0.0127342084595535, 0.00460563222259621, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(695, new CIEXYZ(0.00888589700437563, 0.00321047907550769, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(700, new CIEXYZ(0.00637215303489285, 0.0023011331762594, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(705, new CIEXYZ(0.00455009536631886, 0.00164310557612476, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(710, new CIEXYZ(0.00324806462470549, 0.00117300572197913, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(715, new CIEXYZ(0.00230506002468305, 0.00083249186581398, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(720, new CIEXYZ(0.00162627622573769, 0.00058734432850892, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(725, new CIEXYZ(0.00114944463143723, 0.000415123976214518, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(730, new CIEXYZ(0.000807808818579603, 0.000291708740042634, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(735, new CIEXYZ(0.000560978346235835, 0.000202513182991137, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(740, new CIEXYZ(0.000387075058902726, 0.000139683608212723, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(745, new CIEXYZ(0.000267025692808258, 9.64882755525637E-05, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(750, new CIEXYZ(0.000186244810950297, 6.73174015483002E-05, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(755, new CIEXYZ(0.000131829911365421, 0.000047683159430046, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(760, new CIEXYZ(9.31224054751487E-05, 3.36587007741501E-05, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(765, new CIEXYZ(6.56344665095927E-05, 2.35610905419051E-05, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(770, new CIEXYZ(4.65612027375743E-05, 1.68293503870751E-05, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(775, new CIEXYZ(3.30977224279143E-05, 1.17805452709525E-05, 0)));
            returnValue.Add(new Tuple<double, CIEXYZ>(780, new CIEXYZ(2.35610905419051E-05, 8.41467519353753E-06, 0)));

            return returnValue;
        }

        #region Object Overrides
        /// <summary>
        /// Renvoie une chaine de caractère représentant les valeurs de la structure
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return X + ";" + Y + ";" + Z;
        }
        #endregion

        #region Operators Overloads
        /// <summary>
        /// Ajoute deux couleurs CIEXYZ
        /// </summary>
        /// <param name="cieColor1">Première couleur</param>
        /// <param name="cieColor2">Deuxième couleur</param>
        /// <returns>Retourne la somme des deux couleurs</returns>
        public static CIEXYZ operator +(CIEXYZ cieColor1, CIEXYZ cieColor2)
        {
            return new CIEXYZ(cieColor1.X + cieColor2.X, cieColor1.Y + cieColor2.Y, cieColor1.Z + cieColor2.Z);
        }

        /// <summary>
        /// Multiplie la valeur d'une couleur par un coefficient
        /// </summary>
        /// <param name="cieColor">Couleur à multiplpier</param>
        /// <param name="coeff">Coefficient multiplicateur</param>
        /// <returns>Retourne la couleur multiplée par le coefficient</returns>
        public static CIEXYZ operator *(CIEXYZ cieColor, double coeff)
        {
            return new CIEXYZ(cieColor.X * coeff, cieColor.Y * coeff, cieColor.Z * coeff);
        }
        #endregion
    }
}
